package okhttp3.internal.platform.ohos;

import okhttp3.Protocol;
import okhttp3.internal.platform.OhosPlatform;
import okhttp3.internal.platform.Platform;
import javax.annotation.Nullable;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class OhosSocketAdapter implements SocketAdapter {
    private Method setUseSessionTickets;
    private Method setHostname;
    private Method getAlpnSelectedProtocol;
    private Method setAlpnProtocols;
    private Class sslSocketClass;

    public OhosSocketAdapter(Class sslSocketClass) {
        super();
        this.sslSocketClass = sslSocketClass;
        try {
            setUseSessionTickets = this.sslSocketClass.getDeclaredMethod("setUseSessionTickets", Boolean.TYPE);
            this.setHostname = this.sslSocketClass.getMethod("setHostname", String.class);
            this.getAlpnSelectedProtocol = this.sslSocketClass.getMethod("getAlpnSelectedProtocol");
            this.setAlpnProtocols = this.sslSocketClass.getMethod("setAlpnProtocols", byte[].class);
        } catch (NoSuchMethodException e) {
            e.fillInStackTrace();
        }
    }

    @Override
    public Boolean isSupported() {
        return null != OhosPlatform.buildIfSupported() ? true : false;
    }


    @Override
    public Boolean matchesSocket(SSLSocket sslSocket) {
        return sslSocketClass.isInstance(sslSocket);
    }

    @Override
    public void configureTlsExtensions(SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
        if (matchesSocket(sslSocket)) {
            try {
                try {
                    setUseSessionTickets.invoke(sslSocket, true);
                } catch (IllegalAccessException e) {
                    e.fillInStackTrace();
                } catch (InvocationTargetException e) {
                    e.fillInStackTrace();
                }

                if (hostname != null) {
                    try {
                        setHostname.invoke(sslSocket, hostname);
                    } catch (IllegalAccessException e) {
                        e.fillInStackTrace();
                    } catch (InvocationTargetException e) {
                        e.fillInStackTrace();
                    }
                }

                try {
                    setAlpnProtocols.invoke(
                            sslSocket,
                            Platform.concatLengthPrefixed(protocols)
                    );
                } catch (IllegalAccessException e) {
                    e.fillInStackTrace();
                } catch (InvocationTargetException e) {
                    e.fillInStackTrace();
                }
            } finally {
                return;
            }
        }
    }
    @Override
    public String getSelectedProtocol(SSLSocket sslSocket) {
        if (!matchesSocket(sslSocket)) {
            return null;
        }
        byte[] alpnResult = new byte[0];
        try {
            alpnResult = (byte[]) this.getAlpnSelectedProtocol.invoke(sslSocket);
        } catch (IllegalAccessException e) {
            e.fillInStackTrace();
        } catch (InvocationTargetException e) {
            e.fillInStackTrace();
        }
        String string = new String(alpnResult, StandardCharsets.UTF_8);
        return string;
    }
    @Override
    public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
        return null;
    }
    @Override
    public Boolean matchesSocketFactory(SSLSocketFactory sslSocketFactory) {
        return false;
    }

    static @Nullable
    <T> T readFieldOrNull(Object instance, Class<T> fieldType, String fieldName) {
        for (Class<?> c = instance.getClass(); c != Object.class; c = c.getSuperclass()) {
            try {
                Field field = c.getDeclaredField(fieldName);
                field.setAccessible(true);
                Object value = field.get(instance);
                if (!fieldType.isInstance(value)) {
                    return null;
                }
                return fieldType.cast(value);
            } catch (NoSuchFieldException ignored) {
            } catch (IllegalAccessException e) {
                throw new AssertionError();
            }
        }
        if (!fieldName.equals("delegate")) {
            Object delegate = readFieldOrNull(instance, Object.class, "delegate");
            if (delegate != null){
                return readFieldOrNull(delegate, fieldType, fieldName);
            }
        }
        return null;
    }
}
